/*

ORBITER MODULE: Bump 

Project: surface-physics : http://code.google.com/p/surface-physics/

Description :This file implements the Bump plugin class for the
Orbiter Flight Simulation software. It creates the physics engine that
maintains the entire physics world and its partitions.

Written by Abhijit Nandy
License : Apache License 2.0

*/


#define STRICT
#define ORBITER_MODULE

/*//Memory leak debugging
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
*/



//Windows threading headers
#include <process.h>

//Orbiter Headers
#include "orbitersdk.h"
#include "ModuleAPI.h"

//Orbiter base management
#include "BulletBase/Bump_BulletBase.h"

#include "ThreadSupport/Bump_Thread.h"


//Required to initialize the GLUT part of the engine and the engine itself
#include "Engine/PhysicsEngine.h"
#include "Visualizer/GlutStuff.h"
#include "btBulletDynamicsCommon.h"
#include "Visualizer/GLDebugDrawer.h"

#include "OrulexSDK.h"/*<ORU>*/


/**
 * ==============================================================
 * This module is loaded only once and is shared by all bases and
 * vessels in a scenario. The bases and scenarios insert objects
 * into the physics world created by this plugin as needed and
 * read back their transforms from it, as needed.
 * ==============================================================
 */


/* Thread controls */
const unsigned int SECONDARY_WAIT_COUNT = 50;
HANDLE hThread;
unsigned threadID;
bool renderingThreadRunning  = false;
bool runGLUT = false;

//Semaphore in visualizer mode
bool stepSim = false;





/**
 * Orbiter Interface
 *
 *
 * Operation mode
 * PhysicsMode mode : controls whether to display the GLUT based visualizer or not
 *
 * TerrainType terrainType : This is temporary, used to decide which kind of terrain
 * 					will be loaded for testing the BulletBaseVehicle
 */

PhysicsMode mode = SILENT;


/**
 * The physics engine object, kept global to allow access from callback functions
 * and thread function
 */
PhysicsEngine bump;


//Multithreading tests
//Base *ptrBase[3];
Bump_Thread *baseThread[3];
Bump_ThreadBarrier simStepBarrier(0);


/* Debugging stuff */
char output[100];
static GLDebugDrawer sDebugDraw;


/* Config file for module Bump */
static char *cfgfile = "Modules\\Bump.cfg";




/* ==============================================================
 * Bump class interface
 * ==============================================================
 */


class Bump: public oapi::Module
{

	int numBases;

	//volatile prevents compiler optimizations that may cause threading issues
	//See Dr Dobbs: http://www.drdobbs.com/cpp/184403766
	volatile long simStep;
	volatile int numActiveWorkerThreads;

	Bump_BulletBase brightonBeach;
	Bump_BulletBase olympus;

	void refreshObjectList();


public:
	Bump (HINSTANCE  hModule) : oapi::Module (hModule) {}
	~Bump(){}
	
	
	//Module Overrides
	void	clbkPreStep(double SimT, double SimDT, double MJD);	
	void	clbkSimulationStart(RenderMode mode);
	void	clbkSimulationEnd();
	void	clbkDeleteVessel(OBJHANDLE hVessel);
	
	
};


/**
 * Called only to start the visual debugger and update physics in a separate thread.
 * The visualizer must run in a separate thread as it enters the GLUT loop.
 * Uses the time delta from Orbiter.
 * Global renderingThreadRunning    : set to true as long as this thread is active, reset at exit
 * Global runGLUT : controls physics exit, used to exit from GLUT loop
 *
 */
unsigned __stdcall startPhysicsThread( void *arg )
{
	int rv = 0;
	int argc = 1;
	char *argv[1];

	renderingThreadRunning = true;

	//oapiWriteLog("startPhysicsThread: Secondary engaged ! ");

	argv[0] = new char[10];
	strcpy(argv[0], "Orbiter");

	bump.getDynamicsWorld()->setDebugDrawer(&sDebugDraw);
	runGLUT = true;
	rv = glutmain(argc, argv, 1024, 600,"Bump Physics Visualizer : Powered by Bullet",&bump);

	delete argv[0];


	renderingThreadRunning = FALSE;
	oapiWriteLog("startPhysicsThread: Visualizer exiting ! ");
	return rv;

}


/**
 * ==============================================================
 * Overloaded callback functions
 * ==============================================================
 */


/**
 * --------------------------------------------------------------
 * Plugin initialization
 * --------------------------------------------------------------
 */
DLLCLBK void InitModule  ( HINSTANCE  hModule   )  
{
	oapiWriteLog("Bump: Initializing !");


	InitOrulexSDK();/*<ORU>*/


	//_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

	int intMode = 0;

	//Read the mode from config file
	FILEHANDLE hFile = oapiOpenFile(cfgfile, FILE_IN, CONFIG);
	if (oapiReadItem_int(hFile, "Mode", intMode) ){
		
		mode = (PhysicsMode)intMode;
		sprintf(output, "Bump: Read the mode successfully as %d", mode);
		oapiWriteLog(output);
	}
	else
		mode = SILENT;

	oapiCloseFile (hFile, FILE_IN);


	//Initialize physics in Orbiter thread always(whatever the mode)
	//Any changes to the dynamics world is done in this thread
	bump.initPhysics();

	//Start a thread if visualizer requested, else physics in same thread
	if(mode == VISUALIZE)
		hThread = (HANDLE)_beginthreadex(NULL , 0, &startPhysicsThread, NULL, 0, &threadID);


	oapiRegisterModule(new Bump (hModule));
	
}


/**
 * --------------------------------------------------------------
 * Plugin cleanup :
 * TODO: Unloading Bump leads to crash in multi-threaded mode, check by
 * making simpler multi-threading projects
 * --------------------------------------------------------------
 */
DLLCLBK void ExitModule  ( HINSTANCE  hModule   )  
{		
	int renderingThreadWaitCount = SECONDARY_WAIT_COUNT;

	if(mode == VISUALIZE){
		runGLUT = FALSE;

		while(renderingThreadRunning && renderingThreadWaitCount > 0){
			runGLUT = FALSE;
			oapiWriteLog("Bump: Secondary thread still running... ");
			renderingThreadWaitCount--;
		}

		if(renderingThreadWaitCount == 0){
			TerminateThread(hThread, 0);
			oapiWriteLog("Bump: Secondary thread force terminated. ");
		}
	}
	
	bump.exitPhysics();

	oapiWriteLog("Bump: Exiting");	

	//_CrtDumpMemoryLeaks();

	
}


/**
 * Called to refresh list of objects(only Bases) at the beginning of the sim
 *
 * This function has the following policy:
 * 1. If a vessel of className BulletBase is detected it will create a Bump_BulletBase
 * 		object to manage that base. The Bump_BulletBase object takes over all further management.
 * 2. If any other class ignore it.
 *
 * Currently only Brighton Beach on the Moon gets a BulletBase VESSEL
 *
 * TODO: If other vessel, look for a physics file associated with it
 * TODO: Look for bases and create a BulletBase instead of expecting one in the scenario file
 *
 */
void Bump::refreshObjectList()
{
	int i;
	char strObj[20];
	OBJHANDLE hObj, hObjRbody;



	oapiWriteLog("Bump: refreshObjectList : Refreshing object list");


	hObjRbody = oapiGetObjectByName("Moon");


	// Get the bases on the Moon only, handles to all bases maintained for future expansion
	numBases = oapiGetBaseCount(hObjRbody);
	hObj = new OBJHANDLE[numBases];

	for (i = 0; i < numBases; i++) {

		hObj = oapiGetBaseByIndex (hObjRbody, i);
		oapiGetObjectName(hObj, strObj, 20);

		sprintf(output, "Bump: refreshObjectList : Found %s", strObj);oapiWriteLog(output);

		//Check for the 5 star resort
		if(strcmp(strObj, "Brighton Beach") == 0){
			sprintf(output, "Bump: refreshObjectList : Added %s", strObj);oapiWriteLog(output);

			brightonBeach.init(hObj, hObjRbody);
			brightonBeach.setDynamicsWorld(bump.getDynamicsWorld());
			brightonBeach.setCollisionShapesArray(bump.getCollisionShapesArray());

		}
	}


	hObjRbody = oapiGetObjectByName("Mars");


	// Get the bases on the Moon only, handles to all bases maintained for future expansion
	numBases = oapiGetBaseCount(hObjRbody);
	hObj = new OBJHANDLE[numBases];

	for (i = 0; i < numBases; i++) {

		hObj = oapiGetBaseByIndex (hObjRbody, i);
		oapiGetObjectName(hObj, strObj, 20);

		sprintf(output, "Bump: refreshObjectList : Found %s", strObj);oapiWriteLog(output);

		//Check for the 5 star resort
		if(strcmp(strObj, "Olympus") == 0){
			sprintf(output, "Bump: refreshObjectList : Added %s", strObj);oapiWriteLog(output);

			olympus.init(hObj, hObjRbody);
			olympus.setDynamicsWorld(bump.getDynamicsWorld());
			olympus.setCollisionShapesArray(bump.getCollisionShapesArray());

		}

	}

	//Setup barrier
	numActiveWorkerThreads = 2;
	simStepBarrier.setMaxCount(numActiveWorkerThreads + 1); //+1 for the main thread


	//Start base threads
	baseThread[0] = new Bump_Thread(&brightonBeach, &simStepBarrier);
	baseThread[1] = new Bump_Thread(&olympus, &simStepBarrier);

}


/**
 * Called before advancing Orbiter sim state
 * Reads base command structure for all BulletBases
 * Steps the physics world
 * Sends transform message to BB & BBV (BulletBase & BulletBaseVehicle)
 */
void Bump::clbkPreStep(double SimT, double SimDT, double MJD) 
{
	//Step the physics world, open the semaphore for visualizer mode(its disabled by the thread, till next
	//Orbiter step)
	
	//Prestep may not be needed as its role can be accomplised in the previous iteration itself.
	//Unless there is something imp to be done before the physics step such as AABB comparison ?
	//brightonBeach.prestep(SimT, SimDT, MJD);


	//Each base & vessel has its own engine and its own thread, just start the threads here

	stepSim = true;

	Bump_BulletBase::simStep = simStep;
	Bump_BulletBase::SimT = SimT;
	Bump_BulletBase::SimDT = SimDT;
	Bump_BulletBase::MJD = MJD;

	oapiWriteLogV("---------- Starting Sim Step : %ld(%f, %f, %f) ------------",
			simStep + 1, SimT, SimDT, MJD);

	//Let all threads go here, this is the nth call to the n thread barrier
	simStepBarrier.wait();

	//All threads finish current iteration here, ask barrier if all paused
	simStepBarrier.waitTillNThreadsBlocked(numActiveWorkerThreads);

	//Only main thread active from here on

	++simStep;

	sprintf(output, "Thread 1:%ld\nThread 2:%ld\nSimStep:%d", brightonBeach.count, olympus.count);
	oapiWriteLog(output);

	oapiWriteLogV("Ended Sim Step : %ld", simStep);




	/*for (int i = 0; i < 100; i++) {
		oapiWriteLogV("%d Only main thread running", i);
	}*/






}


/**
 * Called whenever a simulation is started from the Launchpad &
 * when all objects have been inserted
 * Used to get the number of objects in the current sim & their handles.
 */
void Bump::clbkSimulationStart(RenderMode mode) 
{
	oapiWriteLog("Bump: clbkSimulationStart : Sim starting");

	refreshObjectList();

	simStep = 0;


}


/**
 * Called when sim is ended, used to free memory.
 * The module remains in memory throughout the Orbiter main
 * process's lifetime, so its important to reset variables
 * that track individual sims at every simulation exit.
 */
void Bump::clbkSimulationEnd()   
{
	int renderingThreadWaitCount = SECONDARY_WAIT_COUNT;

	oapiWriteLog("Bump: clbkSimulationEnd : Sim ending");
	numBases = 0;

	//New base
	baseThread[0]->stop();
	baseThread[1]->stop();


	//Release all threads for exit rush
	simStepBarrier.releaseAll();

	baseThread[0]->join(0);
	baseThread[1]->join(0);


	//Remove threads
	delete baseThread[0];
	delete baseThread[1];



	sprintf(output, "SimStep = %d", simStep);
	oapiWriteLog(output);


	//Wait for GLUT to exit the moveAndDisplay() for some maximum time
	while(stepSim && renderingThreadWaitCount > 0){
		oapiWriteLog("Bump: clbkSimulationEnd : stepSim is true, GLUT is rendering");
		renderingThreadWaitCount--;
	}
	if(stepSim){	//if the GLUT window is closed then stepSim will never be false, so force stop it
		oapiWriteLog("Bump: clbkSimulationEnd : stepSim is STILL true, FORCE STOPPING");
		stepSim = false;
	}

	//Deallocate as many rigid bodies as possible here
	//bump.clientResetScene();


	//Then go for base object specific stuff
	brightonBeach.exit();
	olympus.exit();






}


/**
 * Called whenever a vessel is deleted from the sim
 * TODO Get vessel and base data again at this point
 */
void Bump::clbkDeleteVessel(OBJHANDLE hVessel)
{
	oapiWriteLog("Bump: clbkDeleteVessel : Vessel deleted. Refreshing object list");

	refreshObjectList();

}


